﻿using System;
using System.Windows.Forms;
using System.Collections.Generic;
using System.Text;
using System.IO;
using SdmxMl.Common;
using SdmxMl.Structure;
using SdmxMl.Manager;

namespace SdmxMl.Excel
{
    /// <summary>
    /// CSV export for Nomenclatures (codelists & Hierarchical Codelists
    /// Each record is one line (with exceptions)
    /// Fields are separated with commas
    /// Leading and trailing space-characters adjacent to comma field separators are ignored
    /// Fields with embedded commas must be delimited with double-quote characters
    /// Fields that contain double quote characters must be surounded by double-quotes, and the embedded double-quotes must each be represented by a pair of consecutive double quotes.
    /// A field that contains embedded line-breaks must be surounded by double-quotes
    /// Fields with leading or trailing spaces must be delimited with double-quote characters
    /// Fields may always be delimited with double quotes
    /// The first record in a CSV file may be a header record containing column (field) names
    /// </summary>
    public class CsvExportNomen : CsvExportBase
    {
        #region Internal Members

        private SmManager sm;
        private SdmxArtefactType arType;

        protected List<string> headerColitems;
        protected AnnotationInventory anInventory;

        protected Dictionary<string, CodeListType> dico;

        #endregion

        #region C'tor
        public CsvExportNomen(CodeListType clt, char sep) : base (sep)
        {
            arType = SdmxArtefactType.CodeLists;
            sm = new SmManager();
            sm.PutArtefact(arType, clt);
        }

        public CsvExportNomen(HierarchicalCodelistType h, char sep)
            : base(sep)
        {
            arType = SdmxArtefactType.HierarchicalCodelists;
            sm = new SmManager();
            sm.PutArtefact(arType, h);
        }

        public CsvExportNomen(SdmxArtefactType at, SmManager smMgr, char sep)
            : base(sep)
        {
            arType = at;
            sm = smMgr;

            if (sm == null)
                throw new ArgumentNullException("sm container cannot be null");
        }
        #endregion

        #region Public Export

        public void Export(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("CSV Export, file path must be specified");

            if (arType == SdmxArtefactType.CodeLists)
                ExportCodelist(filePath);
            else if (arType == SdmxArtefactType.HierarchicalCodelists)
                ExportHierarchy(filePath);

        }
        #endregion

        #region Export Hierarchy

        private void ExportHierarchy(string filePath)
        {
            // Export 1st Hierarchy of 1st hierarchycalcodelist
            ArtefactManager am = sm.GetArtefactManager(arType);
            if (am == null || am.Count == 0)
                throw new ArgumentNullException("CSV Export, Hierarchy Artefact not present in container");

            HierarchicalCodelistType hcl = am.ArtefactList[0] as HierarchicalCodelistType;
            if (hcl.HierarchyList.Count == 0)
                throw new ArgumentNullException("CSV Export, Artefact has no hierarchy defined");

            HierarchyType hierarchy = hcl.HierarchyList[0];

            // Ensure in manager
            ArtefactRefTypedList artl = hcl.ArtefactRefList;
            sm.LoadMissingArtefactsFromReferenceMgr(artl);
            sm.LoadMissingArtefactsFromRegistry(artl);

            dico = GetRefDictionary(hierarchy);

            GenerateHeaderNames();

            using (StreamWriter sw = new StreamWriter(filePath, false, Encoding.Default))
            {
                WriteHeader(sw);

                CreateHierarchyRows(sw, hierarchy.CodeRefList);

            }
        }

        private void GenerateHeaderNames()
        {
            headerColitems = new List<string>(16);

            headerColitems.Add("Code");
            headerColitems.Add("Parent");
            headerColitems.Add("Level");
            headerColitems.Add("Valid from");
            headerColitems.Add("Valid to");

            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                headerColitems.Add("Name_" + lg);

            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                headerColitems.Add("Desc_" + lg);

            anInventory = new AnnotationInventory();
            foreach (KeyValuePair<string, CodeListType> kvp in dico)
                foreach (CodeType c in kvp.Value.CodeList)
                    anInventory.Inventory(c.AnnotationList, true);

            anInventory.GenerateAnnotationHeader(headerColitems);
        }

        private Dictionary<string, CodeListType> GetRefDictionary(HierarchyType hierarchy)
        {
            Dictionary<string, CodeListType> dico = new Dictionary<string, CodeListType>();
            foreach (CodelistRefType crt in hierarchy.CodelistRefList)
            {
                CodeListType clType = null;
                Artefact a = sm.GetArtefact(SdmxArtefactType.CodeLists, crt);
                if (a != null)
                    clType = a as CodeListType;
                if (clType != null)
                    dico.Add(crt.Alias, clType);
            }

            return dico;
        }

        public void CreateHierarchyRows(StreamWriter sw, CodeRefTypeList crtl)
        {
            foreach (CodeRefType it in crtl)
                FillHierarchyRow(sw, string.Empty, it, 0);
        }

        protected void FillHierarchyRow(StreamWriter sw, string parentCode, CodeRefType crt, int level)
        {
            CodeListType clType = dico[crt.CodelistAliasRef];
            ISdmxHierarchyListItem item = clType.CodeList.LocateItem(crt.CodeID);

            if (item == null)
                throw new ApplicationException("Unable to proceed. Hierarchy referred code Id '" +
                    crt.CodeID + "' is not present in codelist: " + clType.FullIdent);

            int col = 1;
            int colCount = headerColitems.Count;

            AddCell(sw, col++, colCount, item.Id, false);
            AddCell(sw, col++, colCount, parentCode, false);
            AddCell(sw, col++, colCount, (level + 1).ToString(), false);
            AddCell(sw, col++, colCount, crt.ValidFrom, false);
            AddCell(sw, col++, colCount, crt.ValidTo, false);

            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                AddCell(sw, col++, colCount, item.Name.GetIfDefinedForLanguage(lg), true);

            MultiLang desc = (item as CodeType).Description;

            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                AddCell(sw, col++, colCount, desc.GetIfDefinedForLanguage(lg), true);

            // All annotations
            AddAnnotations(sw, col, colCount, item.AnnotationList);

            // Recurse
            foreach (CodeRefType it in crt.CodeRefList)
                FillHierarchyRow(sw, crt.CodeID, it, level + 1);
        }


        #endregion

        #region Export Codelist

        private void ExportCodelist(string filePath)
        {
            CodeListType clt = null;

            // Export 1st codelist of matching Artefact nmanager
            ArtefactManager am = sm.GetArtefactManager(arType);
            if (am == null || am.Count == 0)
                throw new ArgumentNullException("CSV Export, Artefact not present in container");

            // Get codelist to export
            clt = am.ArtefactList[0] as CodeListType;

            // Prepare header columns yc annotations
            headerColitems = new List<string>(16);

            headerColitems.Add("Code");
            headerColitems.Add("Parent");
            headerColitems.Add("Level");

            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                headerColitems.Add("Name_" + lg);

            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                headerColitems.Add("Desc_" + lg);

            anInventory = new AnnotationInventory();
            foreach (CodeType c in clt.CodeList)
                anInventory.Inventory(c.AnnotationList, true);

            anInventory.GenerateAnnotationHeader(headerColitems);

            using (StreamWriter sw = new StreamWriter(filePath, false, Encoding.Default))
            {
                WriteHeader(sw);

                CreateRows(sw, clt.CodeList);

            }
        }
        #endregion

        #region Export helpers

        private void WriteHeader(StreamWriter sw)
        {
            //headerColitems
            for (int i = 0; i < headerColitems.Count; i++)
            {
                WriteItem(sw, headerColitems[i], false);
                if (i < headerColitems.Count - 1)
                    sw.Write(delimiter);
                else
                    sw.Write('\n');
            }

        }

        protected void CreateRows(StreamWriter sw, FlatHierarchicalList fhl)
        {
            TreeNode root = new TreeNode();
            fhl.FillTree(root);

            int level = 0;
            foreach (TreeNode node in root.Nodes)
                FillRow(sw, null, node, level);
        }

        protected void FillRow(StreamWriter sw, ISdmxHierarchyListItem parentItem, TreeNode node, int level)
        {
            ISdmxHierarchyListItem item = node.Tag as ISdmxHierarchyListItem;
            int col = 1;
            int colCount = headerColitems.Count;

            AddCell(sw, col++, colCount, item.Id, false);
            AddCell(sw, col++, colCount, parentItem != null ? parentItem.Id : string.Empty, false);
            AddCell(sw, col++, colCount, (level + 1).ToString(), false);


            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                AddCell(sw, col++, colCount, item.Name.GetIfDefinedForLanguage(lg), true);

            CodeType c = item as CodeType;
            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                AddCell(sw, col++, colCount, c.Description.GetIfDefinedForLanguage(lg), true);

            // All annotations
            AddAnnotations(sw, col, colCount, item.AnnotationList);

            // Recurse
            foreach (TreeNode n in node.Nodes)
                FillRow(sw, item, n, level + 1);
        }

        protected void AddAnnotations(StreamWriter sw, int col, int colCount, AnnotationList annotationList)
        {
            // For each annotation, 6 columns ANN_TYPE, ANN_TITLE, ALNN_TEXT_lg..
            foreach (KeyValuePair<string, AnnotationCount> kvp in anInventory.Dico)
            {
                for (int i = 0; i < kvp.Value.Count; i++)
                {
                    Annotation a = annotationList.GetAnnotationType(kvp.Value.Annot.Type, i + 1);
                    if (a != null)
                    {
                        if (kvp.Value.HasTitle)
                            AddCell(sw, col++, colCount, a.Title, true);

                        if (kvp.Value.Internationalized)
                        {
                            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                                AddCell(sw, col++, colCount, a.Text.GetIfDefinedForLanguage(lg), true);
                        }
                        else
                            AddCell(sw, col++, colCount, a.Text.GetFirst(), true);

                        if (kvp.Value.HasUrn && a.Url != null)
                            AddCell(sw, col++, colCount, a.Url.ToString(), true);
                    }
                    else
                    {
                        if (kvp.Value.HasTitle)
                            AddCell(sw, col++, colCount, string.Empty, true);

                        if (kvp.Value.Internationalized)
                        {
                            foreach (string lg in SdmxMl.Common.LangSupport.SupportedLanguages)
                                AddCell(sw, col++, colCount, string.Empty, true);
                        }
                        else
                            AddCell(sw, col++, colCount, string.Empty, true);

                        if (kvp.Value.HasUrn)
                            AddCell(sw, col++, colCount, string.Empty, true);
                    }
                }
            }

        }

        #endregion

    }
}
